using System;
using System.Collections.Generic;
using System.Linq;
using Myotragus.Data.Domain;
using Myotragus.Data.Metadata;
using Myotragus.Data.Util;
using NHibernate;
using NHibernate.Criterion;
using NHibernate.SqlCommand;
using NHibernate.Transform;

namespace Myotragus.Data.Criteria
{
	public class LocalizableQueryBuilder<TEntity> : QueryBuilder, ILocalizableQueryBuilder<TEntity>
		where TEntity : IEntity
	{
		public LocalizableQueryBuilder(ISessionFactoryAdapter sessionFactory,
			ILocalizableEntityDescriptor<TEntity> entityDescriptor,
			ILocalizableEntityProjector<TEntity> projector, ILocalizableAlias alias)
			: base(sessionFactory)
		{
			EntityDescriptor = entityDescriptor;
			Projector = projector;
			Alias = alias;
		}

		protected ILocalizableEntityDescriptor<TEntity> EntityDescriptor { get; private set; }

		protected ILocalizableEntityProjector<TEntity> Projector { get; private set; }

		protected ILocalizableAlias Alias { get; private set; }

		public void AddResultTransformer(ICriteria criteria, Type entityImplementation)
		{
			criteria.SetResultTransformer(Transformers.AliasToBean(entityImplementation));
		}

		public void AddProjection(ICriteria criteria, int lcId)
		{
			var projection = Projector.GetProjection("");
			criteria.SetProjection(projection);
		}

		public void AddAliases(ICriteria criteria, int lcId, IEnumerable<string> properties)
		{
			foreach (var prop in properties)
				Alias.CreateAlias(criteria, "", prop, JoinType.LeftOuterJoin, lcId);
		}

		public void AddMatchingCriteria(ICriteria criteria, object pattern)
		{
			foreach (var prop in ReflectionHelper.GetProperties(pattern))
			{
				if (!EntityDescriptor.EntityProperties.ContainsKey(prop.Name))
					continue;

				var value = prop.GetValue(pattern, ReflectionHelper.EmptyObjects);
				var property = EntityDescriptor.LocalizableProperties.Contains(prop.Name)
									 ? Property.ForName(String.Format("{0}.Value", prop.Name))
									 : Property.ForName(prop.Name);

				criteria.Add(property.Eq(value));
			}
		}

		public void AddLikeCriteria(ICriteria criteria, object pattern)
		{
			foreach (var prop in ReflectionHelper.GetProperties(pattern))
			{
				if (!EntityDescriptor.EntityProperties.ContainsKey(prop.Name))
					continue;

				var value = prop.GetValue(pattern, ReflectionHelper.EmptyObjects);
				var property = EntityDescriptor.LocalizableProperties.Contains(prop.Name)
									 ? Property.ForName(String.Format("{0}.Value", prop.Name))
									 : Property.ForName(prop.Name);

				criteria.Add(prop.PropertyType == ReflectionHelper.TypeOfString
								 ? property.Like(value.ToString(), MatchMode.Anywhere)
								 : property.Eq(value));
			}
		}

		public ICriteria CreateCriteria(int lcId)
		{
			var criteria = CreateCriteria(EntityDescriptor.LoadEntityImplementation);

			AddAliases(criteria, lcId, EntityDescriptor.LocalizableProperties);
			AddProjection(criteria, lcId);
			AddResultTransformer(criteria, EntityDescriptor.LoadEntityImplementation);

			return criteria;
		}

		public IPagedQuery<TEntity> CreateQuery(ICriteria criteria)
		{
			return new CriteriaQuery<TEntity>(criteria);
		}

		public ICriteria CreateMatchingCriteria(int lcId, object pattern)
		{
			var criteria = CreateCriteria(lcId);
			AddMatchingCriteria(criteria, pattern);

			return criteria;
		}

		public ICriteria CreateLikeCriteria(int lcId, object pattern)
		{
			var criteria = CreateCriteria(lcId);
			AddLikeCriteria(criteria, pattern);

			return criteria;
		}
	}
}